﻿/************************************************************************
 * CoreDES - A small and core library for discrete event systems
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     CoreDES\Utils\Choice.cs
 * Description  :     Provides class "Choice" which can be used to have non-deterministic behaviour 
 *                    in one action instance (in non-deterministic models)
 * Change log   :     -10 Dec 2011: initial version
 * To Do        :
 ************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;

namespace CoreDES.Utils
{
    /// <summary>
    /// To support non-deterministic value selection in action instance execution
    /// <para>Note: use this class just in action execution (not precondition, etc.) for non-deterministic models (not stochastic, etc.)</para>
    /// </summary>
    public static class Choice
    {

        #region =-[ internal vars ]=-
        /// <summary>
        /// To Do: rename it!
        /// Usage mode=simulaton/statespace generation
        /// </summary>
        enum Mode
        {
            /// <summary>
            /// currentState space generation mode
            /// </summary>
            ss,
            /// <summary>
            /// simulation mode
            /// </summary>
            sim
        };

        /// <summary>
        /// Choice class is going to be used in which mode? "simulation" or "currentState space generation"
        /// </summary>
        static Mode mode = Mode.ss;

        static Random rnd = new Random();

        static List<Array> dataOfEnums = new List<Array>();

        static List<int> indexes = new List<int>();

        static List<Type> typesOfEnums = new List<Type>();

        static bool _isCalled = false;

        static int inRound = 0;

        static int callInThisRound = 0;
        #endregion

        /// <summary>
        /// Was there any nondeterministic choice? (method "Some" is called?)
        /// </summary>
        public static bool isCalled
        {
            get
            {
                return _isCalled;
            }
        }

        /// <summary>
        /// Number of different possible choices: used in currentState space generation
        /// [To Do] Check compatibility for both typesOfEnums "enum" and "IEnumerable"!
        /// </summary>
        public static int numberOfChoices
        {
            get
            {
                int num = 1;
                foreach (Array arr in dataOfEnums)
                    num *= arr.Length;
                return num;
            }
        }

        /// <summary>
        /// The choice will work on simulation mode (i.e. will select a random choice among a set)
        /// </summary>
        public static void SetSimulatioMode()
        {
            mode = Mode.sim;
        }

        /// <summary>
        /// Choice will work in state space mode (i.e. will select all combinations)
        /// <remarks>Here, you shoud use <see cref="Choice.Reser()"/> and <see cref="Choice.NextRound()"/> </remarks>
        /// </summary>
        public static void SetStateSpaceMode()
        {
            mode = Mode.ss;
        }


        /// <summary>
        /// Reset the choice object (before execution of an action instance for the first time
        /// </summary>
        public static void Reset()
        {
            dataOfEnums.Clear();
            indexes.Clear();
            _isCalled = false;
            inRound = 0;
            callInThisRound = 0;
        }

        /// <summary>
        /// After finishing execution of each action, call this method to go for next inRound 
        /// (reset the choice and start selection the choices from the first)
        /// </summary>
        public static void NextRound()
        {
            inRound++;
            callInThisRound = 0;
        }

        #region For IEnumerable

        /// <summary>
        /// For IEnumerable variables
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="values"></param>
        /// <returns></returns>
        public static T Some<T>(IEnumerable<T> values)
        {
            _isCalled = true;
            if (mode == Mode.sim)
            {
                return Select4Sim<T>(values);
            }
            else
            //* [if (mode == Mode.ss)]
            {
                //* enumerate just in the first inRound!
                if (inRound == 0)
                    Enumerate<T>(values);
                callInThisRound++;

                return Select4SS<T>(values);
            }
        }

        /// <summary>
        /// For a set of values as parameters
        /// </summary>
        /// <typeparam name="T">type of these values</typeparam>
        /// <param name="values">set of values to be selected nondeterministically</param>
        /// <returns></returns>
        public static T Some<T>(params T[] values)
        {
            _isCalled = true;
            if (mode == Mode.sim)
            {
                return Select4Sim<T>(values);
            }
            else //[if (mode == Mode.ss)]
            {
                //enumerate just in the first inRound!
                if (inRound == 0)
                    Enumerate<T>(values);
                callInThisRound++;

                return Select4SS<T>(values);
            }
        }

        static void Enumerate<T>(IEnumerable<T> values)
        {
            Type enumType = typeof(T);
            //not optimize (to be similar to enum): convert to list and then to arrey
            List<T> lst = new List<T>(values);
            Array arr = lst.ToArray();
            dataOfEnums.Add(arr);
            indexes.Add(-1);
            typesOfEnums.Add(enumType);
        }

        static T Select4Sim<T>(IEnumerable<T> values)
        {
            //in .Net 3.5:
            //T[] array = values.Cast<T>().ToArray();
            int count = 0;
            foreach (T value in values)
            {
                count++;
            }
            int i = 0;
            int selected = rnd.Next(count);
            foreach (T value in values)
            {
                i++;
                if (i == selected)
                    return value;
            }

            return default(T);
        }

        static T Select4SS<T>(IEnumerable<T> values)
        {
            int i = (callInThisRound - 1);
            //Base on type && Base on callInThisRound 
            if (dataOfEnums[i].GetType().FullName == typeof(T).FullName + "[]")
            {
                Array arr = dataOfEnums[i];
                indexes[i]++;
                if (indexes[i] == dataOfEnums[i].Length)
                {
                    int j = i;
                    while (j >= 0)
                    {
                        indexes[j]++;
                        if (indexes[j] < dataOfEnums[j].Length)
                            break;
                        else
                        {
                            indexes[j] = 0;
                            //overflow error
                            if (j == 0)
                                break;

                            indexes[j - 1]++;
                            j--;
                        }
                    }
                }

                return (T)arr.GetValue(indexes[i]);
            }
            //error: no item is really selected
            return default(T);
        }

        #endregion

        #region for enum
        /// <summary>
        /// For enum typesOfEnums
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T Some<T>()
        {
            _isCalled = true;
            //if (!typeof(T).IsEnum)
            //{
            //    throw new ArgumentException("_Type '" + enumType.Name + "' is not an enum");
            //}
            if (mode == Mode.sim)
            {
                return Select4Sim<T>();
            }
            else //[if (mode == Mode.ss)]
            {
                //enumerate just in the first inRound!
                if (inRound == 0)
                    Enumerate<T>();
                callInThisRound++;

                return Select4SS<T>();
            }
        }

        /// <summary>
        /// Enumarate an enum data type
        /// </summary>
        /// <typeparam name="T">the enum</typeparam>
        /// <returns>count of members in the enum</returns>
        static void Enumerate<T>()
        {
            Type enumType = typeof(T);
            if (!enumType.IsEnum)
            {
                throw new ArgumentException("Type '" + enumType.Name + "' is not an enum");
            }

            Array arr = Enum.GetValues(typeof(T));
            dataOfEnums.Add(arr);
            indexes.Add(0);
            typesOfEnums.Add(enumType);
        }

        /// <summary>
        /// Select a member among members of an enum which is previusly enumerated
        /// </summary>
        /// <typeparam name="T">enum data type</typeparam>
        /// <returns>selected member</returns>
        static T Select4SS<T>()
        {
            throw new Exception("There is error here!");
            //for (int i = 0; i < dataOfEnums.Count; i++)
            //{
            //    //Base on type && Base on callInThisRound 
            //    if (dataOfEnums[i].GetType().FullName == typeof(T).FullName + "[]" /*&& i == callInThisRound-1*/)
            //    {
            //        Array arr = dataOfEnums[i];
            //        if (i == dataOfEnums.Count - 1)
            //        {
            //            int j = i;
            //            while (j >= 0)
            //            {
            //                indexes[j]++;
            //                if (indexes[j] < dataOfEnums[j].Length)
            //                    break;
            //                else
            //                {
            //                    indexes[j] = 0;
            //                    //overflow error
            //                    if (j == 0)
            //                        break;

            //                    indexes[j - 1]++;
            //                    j--;
            //                }
            //            }
            //        }

            //        return (T)arr.GetValue(indexes[i]);
            //    }
            //}
            ////error: no item is really selected
            //return default(T);
        }

        static T Select4Sim<T>()
        {
            Array arr = Enum.GetValues(typeof(T));
            return (T)arr.GetValue(rnd.Next(arr.Length));
        }

        #endregion
    }

}
